#include "syscall.h"
#include "proc.h"
#include "stdio.h"
#include "type.h"
#include "sysproc.h"
#include "sysmm.h"
#include "string.h"
#include "riscv.h"
#include "fs/sysfile.h"
#include "riscv.h"

typedef struct utsname_t {
    char sysname[65];
    char nodename[65];
    char release[65];
    char version[65];
    char machine[65];
    char domainname[65];
} utsname_t;

utsname_t utsname = {"di", "san", "1.0", "1.0", "riscv64", "102"};

register_t
getParameter(int n) {
    proc_t* p = getCurrentProc();
    switch (n) {
        case 0:
            return p->trapFrame->a0;
        case 1:
            return p->trapFrame->a1;
        case 2:
            return p->trapFrame->a2;
        case 3:
            return p->trapFrame->a3;
        case 4:
            return p->trapFrame->a4;
        case 5:
            return p->trapFrame->a5;
    }
    panic("getParameter");
    return 0;
}

uint64_t
sys_getcwd(void) {
    char* buf = (char*)getParameter(0);
    size_t size = (size_t)getParameter(1);
    char* cwd = sysfile_getcwd(buf, size);
    return (uint64_t)cwd;
}

uint64_t
sys_dup(void) {
    int fd = (int)getParameter(0);
    return (uint64_t)sysfile_dup(fd);
}

uint64_t
sys_dup3(void) {
    int fdOld = (int)getParameter(0);
    int fdNew = (int)getParameter(1);
    return sysfile_dup2(fdOld, fdNew);
}
// const char* __path, uint32_t mode)
uint64_t
sys_mkdirat(void) {
    int fd = (int)getParameter(0);
    const char* path = (const char*)getParameter(1);
    uint32_t mode = (uint32_t)getParameter(2);
    return (uint64_t)sysfile_mkdir(path, mode);
}

uint64_t
sys_unlinkat(void) {}  // TODO: argument
uint64_t
sys_linkat(void) {}  // TODO: argument

uint64_t
sys_umount2(void) {
    char* special = (char*)getParameter(0);
    uint32_t flag = (uint32_t)getParameter(1);
    return (uint64_t)sys_unmount(special, flag);
}

uint64_t
sys_mount1(void) {
    char* special = (char*)getParameter(0);
    char* dir = (char*)getParameter(1);
    char* fstype = (char*)getParameter(2);
    uint32_t flag = (uint32_t)getParameter(3);
    void* data = (void*)getParameter(4);
    return (uint64_t)sys_mount(special, dir, fstype, flag, data);
}

uint64_t
sys_chdir(void) {
    char* name = (char*)getParameter(0);
    return (uint64_t)sysfile_chdir(name);
}

// int sys_openat(void)
// {
//     int fd = (int)getParameter(1);
//     char *path = (char *)getParameter(2);
//     uint32_t flags = (uint32_t)getParameter(3);

// }
uint64_t
sys_openat(void) {
    int fd = (int)getParameter(0);
    const char* __path = (const char*)getParameter(1);
    uint32_t flags = (uint32_t)getParameter(2);
    return (uint64_t)sysfile_open(fd, __path, flags);
}

uint64_t
sys_close(void) {
    int fd = (int)getParameter(0);
    return (uint64_t)sysfile_close(fd);
}

uint64_t
sys_pipe2(void) {}  // TODO: argument

uint64_t
sys_getdents64(void) {
    int fd = (int)getParameter(0);
    dirent_t* __direntp = (dirent_t*)getParameter(1);
    return (uint64_t)sysfile_getDirEntry(fd, __direntp);
}

uint64_t
sys_read(void) {
    int fd = (int)getParameter(0);
    void* buf = (void*)getParameter(1);
    size_t count = (size_t)getParameter(2);
    return (uint64_t)sysfile_read(fd, buf, count);
}

uint64_t
sys_write(void)  // pay attention to ret: byte to write
{
    int fd = (int)getParameter(0);
    void* buf = (void*)getParameter(1);
    size_t count = (size_t)getParameter(2);
    return (uint64_t)sysfile_write(fd, buf, count);
}

uint64_t
sys_fstat(void) {
    int fd = (int)getParameter(0);
    stat_t* stat = (stat_t*)getParameter(1);
    return (uint64_t)sysfile_fstat(fd, stat);
}

uint64_t
sys_exit(void) {
    int ec = (int)getParameter(0);
    sysproc_exit(ec);
    return 0;
}

uint64_t
sys_getpid(void) {
    return (uint64_t)sysproc_getpid();
}

uint64_t
sys_getppid(void) {
    return (uint64_t)sysproc_getppid();
}

uint64_t
sys_clone(void) {
    uint64_t flag = (uint64_t)getParameter(0);
    void* stack = (void*)getParameter(1);
    pid_t ptid = (pid_t)getParameter(2);
    uint64_t tls = (uint64_t)getParameter(3);
    pid_t ctid = (pid_t)getParameter(4);
    return (uint64_t)sysproc_clone(flag, stack, ptid, tls, ctid);
}
uint64_t
sys_execve(void) {
    char* path = (char*)getParameter(0);
    char* argv = (char*)getParameter(1);
    char* envp = (char*)getParameter(2);
    return (uint64_t)sysproc_execve(path, argv, envp);
}

uint64_t
sys_wait4(void) {
    pid_t pid = (pid_t)getParameter(0);
    int* status = (int*)getParameter(1);
    int options = (int)getParameter(2);
    return (uint64_t)sysproc_wait4(pid, status, options);
}

uint64_t
sys_brk(void) {
    address_t brk = (address_t)getParameter(0);
    return (uint64_t)sysmm_brk(brk);
}

uint64_t
sys_munmap(void) {
    void* start = (void*)getParameter(0);
    size_t len = (size_t)getParameter(1);
    return (uint64_t)sysmm_munmap(start, len);
}

uint64_t
sys_mmap(void) {
    void* start = (void*)getParameter(0);
    size_t len = (size_t)getParameter(1);
    int prot = (int)getParameter(2);
    int flags = (int)getParameter(3);
    int fd = (int)getParameter(4);
    uint64_t off = (uint64_t)getParameter(5);
    return (uint64_t)sysmm_mmap(start, len, prot, flags, fd, off);
}

uint64_t
sys_nanosleep(void) {}  // TODO

uint64_t
sys_sched_yield(void) {
    yield();
    return 0;
}

address_t
sys_times(void) {
    void* mytimes = (void*)getParameter(0);
    return sysproc_times(mytimes);
}

uint64_t
sys_uname(void) {
    utsname_t* un = (utsname_t*)getParameter(0);
    memmove(un, &utsname, sizeof(utsname));
    return 0;
}

uint64_t
sys_gettimeofday(void) {
    return r_time();
}

static uint64_t (*syscalls[])(void) = {
    [SYS_getcwd] sys_getcwd,
    [SYS_dup] sys_dup,
    [SYS_dup3] sys_dup3,
    [SYS_mkdirat] sys_mkdirat,
    [SYS_unlinkat] sys_unlinkat,
    [SYS_linkat] sys_linkat,
    [SYS_umount2] sys_umount2,
    [SYS_mount] sys_mount1,  // sys_mount has been used
    [SYS_chdir] sys_chdir,
    [SYS_openat] sys_openat,
    [SYS_close] sys_close,
    [SYS_pipe2] sys_pipe2,
    [SYS_getdents64] sys_getdents64,
    [SYS_read] sys_read,
    [SYS_write] sys_write,
    [SYS_fstat] sys_fstat,
    [SYS_exit] sys_exit,
    [SYS_getpid] sys_getpid,
    [SYS_getppid] sys_getppid,
    [SYS_clone] sys_clone,
    [SYS_execve] sys_execve,
    [SYS_wait4] sys_wait4,
    [SYS_brk] sys_brk,
    [SYS_munmap] sys_munmap,
    [SYS_mmap] sys_mmap,
    [SYS_nanosleep] sys_nanosleep,
    [SYS_sched_yield] sys_sched_yield,
    [SYS_times] sys_times,
    [SYS_uname] sys_uname,
    [SYS_gettimeofday] sys_gettimeofday,
};

void
syscall(void) {
    proc_t* p = getCurrentProc();
    uint64_t syscall_number = p->trapFrame->a7;
    if ((syscall_number > 0) && (syscalls[syscall_number] != 0)) {
        p->trapFrame->a0 = syscalls[syscall_number]();
    } else {
        panic("syscall:unknown syscall");
    }
}
